<script type="text/javascript">
var LBUTTON = 0, MBUTTON = 1, RBUTTON = 2;
var LEFT = 'L', RIGHT = 'R', UP = 'U', DOWN = 'D';

/* ports of all opened tabs */
var ports = {};

var OPTION_URL = chrome.extension.getURL('options.html');

var config, def_config = {
	trigger: RBUTTON,	// mouse button to trigger stroke
	timeout: 200,		// a stroke sequence expires after timeout (milliseconds)
	minstep: 20,		// minimum moving distance of a stroke (in px)
	categories: {
		"navigation": {
			'to-page-top': [UP],
			'to-page-bottom': [DOWN],
			'history-back': [LEFT],
			'history-forward': [RIGHT],
			'previous-tab': [UP, LEFT],
			'next-tab': [UP, RIGHT]
		},

		"window": {
			//'minimize-window': [DOWN, LEFT],
			'new-window': [UP, DOWN, RIGHT, UP],
			'close-window': [LEFT, DOWN, RIGHT]
		},

		"tab": {
			'new-tab': [RIGHT, DOWN],
			'close-tab': [LEFT, UP, RIGHT],
			'detach-tab': [LEFT, UP]
		},

		"utilities": {
			'reload': [UP, DOWN],
			'skip-cache-reload': [UP, DOWN, UP],
			'view-source': [LEFT, DOWN, RIGHT, DOWN, LEFT]
		},

		"other": {
			'mouse-stroke-options': [DOWN, UP, RIGHT, DOWN]
		}
	}
};

/* stroke handlers */
var actions = {
	'to-page-top': function(tab) {
		tab_exec(tab, 'document.body.scrollTop = 0');
	},
	'to-page-bottom': function(tab) {
		tab_exec(tab, 'document.body.scrollTop = document.body.scrollHeight');
	},
	'history-back': function(tab) {
		tab_exec(tab, 'history.back()');
	},
	'history-forward': function(tab) {
		tab_exec(tab, 'history.forward()');
	},
	'previous-tab': function(tab) {
		chrome.tabs.getAllInWindow(tab.windowId, function(tabs) {
			for (var i = 0; i < tabs.length; ++i) {
				if (tabs[i].id == tab.id) {
					if (!!(tab = tabs[i - 1])) {
						chrome.tabs.update(tab.id, {selected: true});
					}
					break;
				}
			}
		});
	},
	'next-tab': function(tab) {
		chrome.tabs.getAllInWindow(tab.windowId, function(tabs) {
			for (var i = 0; i < tabs.length; ++i) {
				if (tabs[i].id == tab.id) {
					if (!!(tab = tabs[i + 1])) {
						chrome.tabs.update(tab.id, {selected: true});
					}
					break;
				}
			}
		});
	},
	'minimize-window': function(tab) {
//		chrome.windows.getCurrent(function(wnd) {
//			chrome.windows.update(wnd.id, { left: 0, top: 0, width: 0, height: 0 });
//		});
	},
	'new-window': function(tab) {
		chrome.windows.create();
	},
	'close-window': function(tab) {
		chrome.windows.getCurrent(function(wnd) {
			chrome.windows.remove(wnd.id);
		});
	},
	'new-tab': function(tab) {
		chrome.tabs.create({
			"windowId": tab.windowId,
			"index": tab.index + 1,
			"selected": true
		});
	},
	'close-tab': function(tab) {
		chrome.tabs.remove(tab.id);
	},
	'detach-tab': function(tab) {
		chrome.windows.create(null, function(wnd) {
			chrome.tabs.move(tab.id, { windowId: wnd.id, index: 0 });
			/* remove all other tabs */
			chrome.tabs.getAllInWindow(wnd.id, function(tabs) {
				for (var i = 0; i < tabs.length; ++i) {
					if (tabs[i].id != tab.id) {
						chrome.tabs.remove(tabs[i].id);
					}
				}
			});
		});
	},
	'reload': function(tab) {
		tab_exec(tab, 'location.reload()');
	},
	'skip-cache-reload': function(tab) {
		tab_exec(tab, 'location.reload(true)');
	},
	'view-source': function(tab) {
		chrome.tabs.create({
			url: 'view-source:' + tab.url
		});
	},
	'mouse-stroke-options': function(tab) {
		chrome.tabs.create({
			url: OPTION_URL
		});
	}
};

/* compare stroke sequences */
var match = function(seq1, seq2) {
	if (seq1 === seq2 || !seq1 && !seq2) return true;
	if (!seq1 && seq2 || seq1 && !seq2 || seq1.length != seq2.length) return false;

	return seq1.join(' ') == seq2.join(' ');
}

/* load user config */
var load_config = function(update_tabs) {
	try {
		if (!(config = JSON.parse(localStorage.getItem('config')))) {
			config = def_config;
		}
	} catch (ex) {
		console.error(ex);
		config = def_config;
	}

	if (!update_tabs) return;

	/* update config in all tabs */
	chrome.windows.getAll({populate: true}, function(windows) {
		var i, j, tabs;
		for (i = 0; i < windows.length; ++i) {
			tabs = windows[i].tabs;
			for (j = 0; j < tabs.length; ++j) {
				if (ports[tabs[j].id]) {
					ports[tabs[j].id].config.postMessage(config);
				}
			}
		}
	});
}

/* execute the given script in the given tab */
var tab_exec = function(tab, script)
{
	ports[tab.id].script.postMessage(script);
}

/* routine to execute when a tab is updated */
chrome.tabs.onUpdated.addListener(function(id, info) {
	if ('complete' !== info.status) return;

	/* create ports */
	ports[id] = {};
	/* push configurations */
	ports[id].config = chrome.tabs.connect(id, {name: 'stroke.js-config'});
	ports[id].config.postMessage(config);
	/* create port for script tunneling */
	ports[id].script = chrome.tabs.connect(id, {name: 'stroke.js-script'});
});

/* routine to execute when a tab is destroied */
chrome.tabs.onRemoved.addListener(function(id) {
	/* remove all ports associated with this tab */
	delete ports[id];
});

/* listen to the content script */
chrome.extension.onConnect.addListener(function(port) {
	if ('stroke.html-stroke' != port.name) return;
	/* don't respond to option page */
	if (port.tab.url == OPTION_URL) return;

	port.onMessage.addListener(function(strokes) {
		var i, tab = port.sender.tab;

		/* accept strokes */
		for (id in config.strokes) {
			if (match(strokes, config.strokes[id])) {
				actions[id].call(tab, tab);
				break;
			}
		}
	});
});

/* listen to the config page */
chrome.extension.onConnect.addListener(function(port) {
	if ('stroke.html-config' != port.name) return;

	port.onMessage.addListener(function(command) {
		if ('reload' == command) {
			load_config(true);
		}
	});
});

/* always refresh def_config */
localStorage.setItem('def_config', JSON.stringify(def_config));
/* get user config */
load_config();
</script>